﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace WebPALTT.Lib
{
    /*
     * Class: ActionItemCollection
     * 
     * This class contains a collection of @link[ActionItem.cs.htm, ActionItem] objects.
     *      
     * The class inherits from IList, ICollection and IEnumerable.
     */
    [Serializable()]
    public class ActionItemCollection
        : IList<ActionItem>, ICollection<ActionItem>, IEnumerable<ActionItem>, ISerializable
    {
        /*
         * Method: {{this[int index]}}
         * 
         * Gets or sets the ActionItem by index.
         * 
         * Method: {{this[string name]}}
         * 
         * Gets or sets the ActionItem by name.
         * 
         * Method: {{IndexOf[string name]}}
         * 
         * Returns the index of the ActionItem by name. Returns -1 if not found.
         */

        public IEnumerator<ActionItem> GetEnumerator() { return mActions.GetEnumerator(); }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return mActions.GetEnumerator(); }

        public ActionItemCollection(SerializationInfo info, StreamingContext ctxt)
        {
            mActions = (List<ActionItem>)info.GetValue("Actions", typeof(List<ActionItem>));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            info.AddValue("Actions", mActions);
        }

        List<ActionItem> mActions = new List<ActionItem> { };

        public ActionItemCollection() { }

        public int Count { get { return mActions.Count; } }

        public bool IsReadOnly { get { return false; } }

        public ActionItem this[int index] { get { return mActions[index]; } set { mActions[index] = value; } }

        public ActionItem this[string name]
        {
            get
            {
                int index = IndexOf(name);
                if (index >= 0)
                    return mActions[index];
                return null;
            }
            set
            {
                int index = IndexOf(name);
                if (index >= 0)
                    mActions[index] = value;
                else
                    throw new IndexOutOfRangeException("Index with name [" + name + "] was not found and could not be assigned to.");
            }
        }

        public ActionItem this[Guid key]
        {
            get
            {
                int index = IndexOf(key);
                if (index >= 0)
                    return mActions[index];                
                return null;
            }
            set
            {
                int index = IndexOf(key);
                if (index >= 0)
                    mActions[index] = value;
                else
                    throw new IndexOutOfRangeException("Index with key {" + key.ToString() + "} was not found and could not be assigned to.");
            }
        }

        public void Add(ActionItem item) 
        { 
            mActions.Add(item);
            RefreshIndexes();
        }

        public void AddRange(IEnumerable<ActionItem> collection) 
        { 
            mActions.AddRange(collection);
            RefreshIndexes();
        }

        public void Clear() { mActions.Clear(); }

        public bool Contains(ActionItem item) { return mActions.Contains(item); }

        public bool Contains(Guid key, bool recursive = false)
        {
            foreach (ActionItem item in mActions)
            {
                if (Guid.Equals(item.Key, key))
                    return true;
                if (recursive && item.ActionType == ActionType.If)
                { 
                    if(((ActionItem_If)item).ThenActions.Contains(key, true))
                        return true;
                    if (((ActionItem_If)item).ElseActions.Contains(key, true))
                        return true;
                }
                else if (recursive && item.ActionType == ActionType.Loop)
                {
                    if (((ActionItem_Loop)item).Actions.Contains(key, true))
                        return true;
                }
            }
            return false;
        }

        public bool Contains(ActionItem value, IEqualityComparer<ActionItem> comparer) { return mActions.Contains(value, comparer); }

        public void CopyTo(ActionItem[] array, int arrayIndex) { mActions.CopyTo(array, arrayIndex); }

        public ActionItem[] FindAll(Predicate<ActionItem> match, bool includeChildren = false)
        {
            List<ActionItem> list = new List<ActionItem> { };
            foreach (ActionItem ai in mActions)
            {
                if (match(ai))
                    list.Add(ai);
                if (includeChildren && ai.ActionType == ActionType.If)
                {
                    list.AddRange(((ActionItem_If)ai).ThenActions.FindAll(match, true));
                    list.AddRange(((ActionItem_If)ai).ElseActions.FindAll(match, true));
                }
                else if (includeChildren && ai.ActionType == ActionType.Loop)
                    list.AddRange(((ActionItem_Loop)ai).Actions.FindAll(match, true));
            }
            return list.ToArray();
        }

        public int IndexOf(ActionItem value) { return mActions.IndexOf(value); }

        public int IndexOf(string name)
        {
            for (int i = 0; i < mActions.Count; i++)
                if ( mActions[i].Name.Equals(name)) { return i; }
            return -1;
        }

        public int IndexOf(Guid key)
        {
            for (int i = 0; i < mActions.Count; i++)
                if (Guid.Equals(mActions[i].Key, key)) { return i; }
            return -1;
        }

        public void Insert(int index, ActionItem value) 
        { 
            mActions.Insert(index, value);
            RefreshIndexes();
        }

        public bool Remove(ActionItem value) 
        { 
            bool removed = mActions.Remove(value);
            RefreshIndexes();
            return removed;
        }

        public bool Remove(string name)
        {
            int index = IndexOf(name);
            if (index >= 0)
            {
                mActions.RemoveAt(index);
                RefreshIndexes();
                return true;
            }
            return false;
        }

        public bool Remove(Guid key)
        {
            int index = IndexOf(key);
            if (index >= 0)
            {
                mActions.RemoveAt(index);
                RefreshIndexes();
                return true;
            }
            return false;
        }

        public void RemoveAt(int index) 
        {
            mActions.RemoveAt(index);
            RefreshIndexes();
        }

        /// <summary>
        /// Moves the item at index up the order (index 1 will become 0).
        /// </summary>
        /// <param name="index"></param>
        public void MoveUp(int index)
        {
            if (index < 0 || index >= mActions.Count)
                throw new IndexOutOfRangeException();
            if (index == 0)
                return;
            ActionItem tmp = mActions[index - 1];
            mActions[index - 1] = mActions[index];
            mActions[index] = tmp;

            mActions[index - 1].SetIndex(index - 1);
            mActions[index].SetIndex(index);
        }
        /// <summary>
        /// Moves the item at index down the order (index 1 will become 2).
        /// </summary>
        /// <param name="index"></param>
        public void MoveDown(int index)
        {
            if (index < 0 || index >= mActions.Count)
                throw new IndexOutOfRangeException();
            if (index == mActions.Count - 1)
                return;
            ActionItem tmp = mActions[index + 1];
            mActions[index + 1] = mActions[index];
            mActions[index] = tmp;

            mActions[index].SetIndex(index);
            mActions[index + 1].SetIndex(index + 1);
        }

        public ActionItem[] GetScriptActions(bool recursive = true)
        {
            List<ActionItem> list = new List<ActionItem> { };
            for (int i = 0; i < mActions.Count; i++)
            {
                switch (mActions[i].ActionType)
                {
                    case ActionType.Script:
                    case ActionType.Variable:
                        list.Add(mActions[i]);
                        break;
                    case ActionType.If:
                        list.Add(mActions[i]);
                        if (recursive)
                        {
                            list.AddRange(((ActionItem_If)mActions[i]).ThenActions.GetScriptActions());
                            list.AddRange(((ActionItem_If)mActions[i]).ElseActions.GetScriptActions());
                        }
                        break;
                    case ActionType.Loop:
                        if (recursive)
                            list.AddRange(((ActionItem_Loop)mActions[i]).Actions.GetScriptActions());
                        break;
                    default:
                        break;
                }
            }
            return list.ToArray();
        }

        /// <summary>
        /// Recursively Corrects the ActionItem.Parent and ActionItem.Index fields.
        /// Leaves the current ActionItem.Parent of Actions directly in this collection if setParent is flase (default).
        /// </summary>
        public void RefreshIndexes(bool setParent = false, ActionItem parent = null)
        {
            for (int i = 0; i < mActions.Count; i++)
            {
                if (!setParent)
                    mActions[i].SetIndex(i);
                else
                    mActions[i].SetParent(parent, i);
            }
        }

        public ActionItem GetChild(Guid key)
        {
            ActionItem value = null;
            foreach (ActionItem item in mActions)
            {
                if (Guid.Equals(item.Key, key))
                    value = item;
                else if (item.ActionType == ActionType.If || item.ActionType == ActionType.Loop)
                    value = item.GetChild(key);
                if (value != null)
                    return value;
            }
            return null;
        }
    }
}
